import os
import sys
from functools import wraps
import json
import ipaddress
import click
from loguru import logger

from . import VERSION
from .tfm import TinyFileManagerApp

default_config = {
    'Port': 8000,
    'Ip': '0.0.0.0',
    'Directory': os.getcwd(),
    'ShowHidden': True,
    'CalculateFolderSize': True,
    'CopyConflictRename': True,
}


def return_if_none(f):
    @wraps(f)
    def wrapper(ctx, param, value):
        if value is None:
            return None
        return f(ctx, param, value)
    return wrapper


@return_if_none
def validate_port(ctx, param, value):
    try:
        port = int(value)
        if port <= 0 or port > 65535:
            raise ValueError
        return port
    except ValueError:
        raise click.BadParameter('not a valid port number')
    pass


@return_if_none
def validate_config(ctx, param, value):
    try:
        config_filepath = value
        with open(config_filepath, encoding='utf-8') as fp:
            config = json.load(fp)
        return config
    except Exception as e:
        sys.stderr.write(str(e) + '\n')
        raise click.BadParameter('failed to load config')
    pass


@return_if_none
def validate_ip(ctx, param, value):
    try:
        ipaddress.ip_address(value)
        return value
    except ValueError:
        raise click.BadParameter('not a valid ip address')
    pass


@return_if_none
def validate_dir_path(ctx, param, value):
    try:
        path = value
        if not os.path.isdir(path):
            raise ValueError
        return path
    except ValueError:
        raise click.BadParameter('not a valid directory')
    pass


def print_version(ctx, param, value):
    if not value or ctx.resilient_parsing:
        return
    click.echo(f'TinyFileManager-Py v{VERSION}')
    ctx.exit()
    pass


@click.command()
@click.option('--version', '-v', is_flag=True, callback=print_version,
              expose_value=False, is_eager=True)
@click.argument('port', default=None, required=False, nargs=1, type=int,
                callback=validate_port)
@click.option('--bind', '-b', default=None, type=str, metavar='ADDRESS',
              help='specify alternate bind address.', callback=validate_ip)
@click.option('--config', '-c', default=None, type=click.Path(exists=True),
              metavar='CONFIG FILE', help='specify config file.', callback=validate_config)
@click.option('--directory', '-d', default=None, type=str, metavar='DIR',
              help='specify alternate directory.', callback=validate_dir_path)
def main(port, bind, config, directory):
    config_dict = dict(default_config)

    # load config first so other cli options can mask it
    if config is not None:
        config_dict.update(config)
    if port is not None:
        config_dict['Port'] = port
    if bind is not None:
        config_dict['Ip'] = bind
    if directory is not None:
        config_dict['Directory'] = directory
    
    logger.debug(f'Config: {config_dict}')

    TinyFileManagerApp(config_dict).run()
    pass


if __name__ == '__main__':
    main()
